﻿/*  Copyright (c) 2012 William Rogers and James Boud

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

using System;
using SlimDX;
using SlimDX.Direct3D11;

namespace JBBRXG11
{
    /// <summary>
    /// Contains depth-stencil state for the device.
    /// </summary>
    public class DepthStencilState : IDisposable
    {
        /// <summary>
        /// A built-in state object with settings for not using a depth stencil
        /// buffer.
        /// </summary>
        public static DepthStencilState None;

        /// <summary>
        /// A built-in state object with default settings for using a depth stencil
        /// buffer.
        /// </summary>
        public static DepthStencilState Default;

        /// <summary>
        /// A built-in state object with settings for enabling a read-only depth
        /// stencil buffer.
        /// </summary>
        public static DepthStencilState DepthRead;

        static DepthStencilState()
        {
            None = new DepthStencilState();
            None.DepthBufferEnable = false;
            None.DepthBufferWriteEnable = false;

            Default = new DepthStencilState();

            DepthRead = new DepthStencilState();
            DepthRead.DepthBufferWriteEnable = false;
        }

        internal DepthStencilStateDescription dssdesc;
        internal DepthStencilOperationDescription dssbackdesc, dssfrontdesc;
        int referencestencil;
        bool twosidedstencilmode;
        SlimDX.Direct3D11.DepthStencilState dxdepthstencilstate;

        internal SlimDX.Direct3D11.DepthStencilState Get_DX_DepthStencilState(Device dx_dev)
        {
            if (dxdepthstencilstate == null)
            {
                dssdesc.BackFace = dssbackdesc;
                dssdesc.FrontFace = dssfrontdesc;
                dxdepthstencilstate = SlimDX.Direct3D11.DepthStencilState.FromDescription(dx_dev, dssdesc);
            }
            return dxdepthstencilstate;
        }

        private void modified()
        {
            if (dxdepthstencilstate != null)
            {
                dxdepthstencilstate.Dispose();
                dxdepthstencilstate = null;
            }
        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //////////////////////////////////////// Constructors ////////////////////////////////////////////////////////
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public DepthStencilState()
        {
            dssbackdesc = new DepthStencilOperationDescription();
            dssbackdesc.Comparison = Comparison.Always;
            dssbackdesc.DepthFailOperation = StencilOperation.Keep;
            dssbackdesc.FailOperation = StencilOperation.Keep;
            dssbackdesc.PassOperation = StencilOperation.Keep;

            dssfrontdesc = new DepthStencilOperationDescription();
            dssfrontdesc.Comparison = Comparison.Always;
            dssfrontdesc.DepthFailOperation = StencilOperation.Keep;
            dssfrontdesc.FailOperation = StencilOperation.Keep;
            dssfrontdesc.PassOperation = StencilOperation.Keep;

            dssdesc = new DepthStencilStateDescription();
            dssdesc.IsStencilEnabled = false;
            dssdesc.StencilReadMask = byte.MaxValue;
            dssdesc.StencilWriteMask = byte.MaxValue;

            dssdesc.DepthComparison = Comparison.LessEqual;
            dssdesc.DepthWriteMask = DepthWriteMask.All;
            dssdesc.IsDepthEnabled = true;

            dxdepthstencilstate = null;
            referencestencil = 0;
            twosidedstencilmode = false;
            GraphicsDeviceManager.GetGame(Game.GraphicsDevice).UserContentAdd(this);
        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //////////////////////////////////////// Properties //////////////////////////////////////////////////////////
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Gets or sets the stencil operation to perform if the stencil test passes
        /// and the depth-buffer test fails for a counterclockwise triangle. The
        /// default is StencilOperation.Keep.
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.StencilOperation CounterClockwiseStencilDepthBufferFail
        {
            get { return EnumConversions.convert(dssbackdesc.DepthFailOperation); }
            set { dssbackdesc.DepthFailOperation = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Gets or sets the stencil operation to perform if the stencil test fails
        /// for a counterclockwise triangle. The default is StencilOperation.Keep.
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.StencilOperation CounterClockwiseStencilFail
        {
            get { return EnumConversions.convert(dssbackdesc.DepthFailOperation); }
            set { dssbackdesc.DepthFailOperation = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Gets or sets the stencil operation to perform if the stencil and
        /// depth-tests pass for a counterclockwise triangle. The default is
        /// StencilOperation.Keep.
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.StencilOperation CounterClockwiseStencilPass
        {
            get { return EnumConversions.convert(dssbackdesc.PassOperation); }
            set { dssbackdesc.PassOperation = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Gets or sets the comparison function to use for counterclockwise stencil
        /// tests. The default is CompareFunction.Always.
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.CompareFunction CounterClockwiseStencilFunction
        {
            get { return EnumConversions.convert(dssbackdesc.Comparison); }
            set { dssbackdesc.Comparison = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Gets or sets the stencil operation to perform if the stencil test passes
        /// and the depth-test fails. The default is StencilOperation.Keep.
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.StencilOperation StencilDepthBufferFail
        {
            get { return EnumConversions.convert(dssfrontdesc.DepthFailOperation); }
            set { dssfrontdesc.DepthFailOperation = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Gets or sets the stencil operation to perform if the stencil test fails.
        /// The default is StencilOperation.Keep.
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.StencilOperation StencilFail
        {
            get { return EnumConversions.convert(dssfrontdesc.FailOperation); }
            set { dssfrontdesc.FailOperation = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Gets or sets the stencil operation to perform if the stencil test passes.
        /// The default is StencilOperation.Keep.
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.StencilOperation StencilPass
        {
            get { return EnumConversions.convert(dssfrontdesc.PassOperation); }
            set { dssfrontdesc.PassOperation = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Gets or sets the comparison function for the stencil test. The default is
        /// CompareFunction.Always.
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.CompareFunction StencilFunction
        {
            get { return EnumConversions.convert(dssfrontdesc.Comparison); }
            set { dssfrontdesc.Comparison = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Gets or sets stencil enabling. The default is false.
        /// </summary>
        public bool StencilEnable { get { return dssdesc.IsStencilEnabled; } set { dssdesc.IsStencilEnabled = value; modified(); } }

        /// <summary>
        /// Gets or sets the mask applied to the reference value and each stencil
        /// buffer entry to determine the significant bits for the stencil test. The
        /// default mask is Int32.MaxValue.
        /// </summary>
        public int StencilMask { get { return dssdesc.StencilReadMask; } set { dssdesc.StencilReadMask = (byte)value; modified(); } }

        /// <summary>
        /// Gets or sets the write mask applied to values written into the stencil
        /// buffer. The default mask is Int32.MaxValue.
        /// </summary>
        public int StencilWriteMask { get { return dssdesc.StencilWriteMask; } set { dssdesc.StencilWriteMask = (byte)value; modified(); } }

        /// <summary>
        /// Specifies a reference value to use for the stencil test. The default is 0.
        /// </summary>
        public int ReferenceStencil { get { return referencestencil; } set { referencestencil = value; modified(); } }

        /// <summary>
        /// Enables or disables two-sided stenciling. The default is false.
        /// </summary>
        public bool TwoSidedStencilMode { get { return twosidedstencilmode; } set { twosidedstencilmode = value; modified(); } }

        /// <summary>
        /// Enables or disables depth buffering. The default is true.
        /// </summary>
        public bool DepthBufferEnable { get { return dssdesc.IsDepthEnabled; } set { dssdesc.IsDepthEnabled = value; modified(); } }

        /// <summary>
        /// Gets or sets the comparison function for the depth-buffer test. The
        /// default is CompareFunction.LessEqual
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.CompareFunction DepthBufferFunction
        {
            get { return EnumConversions.convert(dssdesc.DepthComparison); }
            set { dssdesc.DepthComparison = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Enables or disables writing to the depth buffer. The default is true.
        /// </summary>
        public bool DepthBufferWriteEnable
        {
            get { return dssdesc.DepthWriteMask == DepthWriteMask.All; }
            set { dssdesc.DepthWriteMask = (value ? DepthWriteMask.All : DepthWriteMask.Zero); modified(); }
        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //////////////////////////////////////// Methods /////////////////////////////////////////////////////////////
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Immediately releases the unmanaged resources used by this object.
        /// </summary>
        public void Dispose()
        {
            if (dxdepthstencilstate != null)
            {
                dxdepthstencilstate.Dispose();
            }
        }
    }
}
